Leaflet Blog in Deno Fresh
1/** @jsxImportSource preact */ 2import { CSS, render } from "@deno/gfm"; 3import { Handlers, PageProps } from "$fresh/server.ts"; 4 5import { Layout } from "../../islands/layout.tsx"; 6import { PostInfo } from "../../components/post-info.tsx"; 7import { Title } from "../../components/typography.tsx"; 8import { getPost } from "../../lib/api.ts"; 9import { Head } from "$fresh/runtime.ts"; 10 11interface Post { 12 uri: string; 13 value: { 14 title: string; 15 content: string; 16 createdAt: string; 17 }; 18} 19 20// Only override backgrounds in dark mode to make them transparent 21const transparentDarkModeCSS = ` 22@media (prefers-color-scheme: dark) { 23 .markdown-body { 24 color: white; 25 background-color: transparent; 26 } 27 28 .markdown-body a { 29 color: #58a6ff; 30 } 31 32 .markdown-body blockquote { 33 border-left-color: #30363d; 34 background-color: transparent; 35 } 36 37 .markdown-body pre, 38 .markdown-body code { 39 background-color: transparent; 40 color: #c9d1d9; 41 } 42 43 .markdown-body table td, 44 .markdown-body table th { 45 border-color: #30363d; 46 background-color: transparent; 47 } 48} 49 50.font-sans { font-family: var(--font-sans); } 51.font-serif { font-family: var(--font-serif); } 52.font-mono { font-family: var(--font-mono); } 53 54.markdown-body h1 { 55 font-family: var(--font-serif); 56 text-transform: uppercase; 57 font-size: 2.25rem; 58} 59 60.markdown-body h2 { 61 font-family: var(--font-serif); 62 text-transform: uppercase; 63 font-size: 1.75rem; 64} 65 66.markdown-body h3 { 67 font-family: var(--font-serif); 68 text-transform: uppercase; 69 font-size: 1.5rem; 70} 71 72.markdown-body h4 { 73 font-family: var(--font-serif); 74 text-transform: uppercase; 75 font-size: 1.25rem; 76} 77 78.markdown-body h5 { 79 font-family: var(--font-serif); 80 text-transform: uppercase; 81 font-size: 1rem; 82} 83 84.markdown-body h6 { 85 font-family: var(--font-serif); 86 text-transform: uppercase; 87 font-size: 0.875rem; 88} 89`; 90 91export const handler: Handlers<Post> = { 92 async GET(_req, ctx) { 93 try { 94 const { slug } = ctx.params; 95 const post = await getPost(slug); 96 return ctx.render(post); 97 } catch (error) { 98 console.error("Error fetching post:", error); 99 return new Response("Post not found", { status: 404 }); 100 } 101 }, 102}; 103 104export default function BlogPage({ data: post }: PageProps<Post>) { 105 if (!post) { 106 return <div>Post not found</div>; 107 } 108 109 return ( 110 <> 111 <Head> 112 <title>{post.value.title} knotbin</title> 113 <meta name="description" content="by Roscoe Rubin-Rottenberg" /> 114 {/* Merge GFM’s default styles with our dark-mode overrides */} 115 <style 116 dangerouslySetInnerHTML={{ __html: CSS + transparentDarkModeCSS }} 117 /> 118 </Head> 119 120 <Layout> 121 <div class="p-8 pb-20 gap-16 sm:p-20"> 122 <link rel="alternate" href={post.uri} /> 123 <div class="max-w-[600px] mx-auto"> 124 <a 125 href="/" 126 class="hover:underline hover:underline-offset-4 font-medium block mb-8" 127 > 128 Back 129 </a> 130 <article class="w-full space-y-8"> 131 <div class="space-y-4 w-full"> 132 <Title>{post.value.title}</Title> 133 <PostInfo 134 content={post.value.content} 135 createdAt={post.value.createdAt} 136 includeAuthor 137 class="text-sm" 138 /> 139 <div class="diagonal-pattern w-full h-3" /> 140 </div> 141 <div class="[&>.bluesky-embed]:mt-8 [&>.bluesky-embed]:mb-0"> 142 <div 143 class="mt-8 markdown-body" 144 dangerouslySetInnerHTML={{ 145 __html: render(post.value.content), 146 }} 147 /> 148 </div> 149 </article> 150 </div> 151 </div> 152 </Layout> 153 </> 154 ); 155}